//! ZigObject encapsulates the state of the incrementally compiled Zig module.
//! It stores the associated input local and global symbols, allocated atoms,
//! and any relocations that may have been emitted.
//! Think about this as fake in-memory Object file for the Zig module.

path: []const u8,
/// Index within the list of relocatable objects of the linker driver.
index: File.Index,
/// Map of all `Decl` that are currently alive.
/// Each index maps to the corresponding `DeclInfo`.
decls_map: std.AutoHashMapUnmanaged(InternPool.DeclIndex, DeclInfo) = .{},
/// List of function type signatures for this Zig module.
func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
/// List of `std.wasm.Func`. Each entry contains the function signature,
/// rather than the actual body.
functions: std.ArrayListUnmanaged(std.wasm.Func) = .{},
/// List of indexes pointing to an entry within the `functions` list which has been removed.
functions_free_list: std.ArrayListUnmanaged(u32) = .{},
/// Map of symbol locations, represented by its `types.Import`.
imports: std.AutoHashMapUnmanaged(Symbol.Index, types.Import) = .{},
/// List of WebAssembly globals.
globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
/// Mapping between an `Atom` and its type index representing the Wasm
/// type of the function signature.
atom_types: std.AutoHashMapUnmanaged(Atom.Index, u32) = .{},
/// List of all symbols generated by Zig code.
symbols: std.ArrayListUnmanaged(Symbol) = .{},
/// Map from symbol name offset to their index into the `symbols` list.
global_syms: std.AutoHashMapUnmanaged(u32, Symbol.Index) = .{},
/// List of symbol indexes which are free to be used.
symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .{},
/// Extra metadata about the linking section, such as alignment of segments and their name.
segment_info: std.ArrayListUnmanaged(types.Segment) = .{},
/// List of indexes which contain a free slot in the `segment_info` list.
segment_free_list: std.ArrayListUnmanaged(u32) = .{},
/// File encapsulated string table, used to deduplicate strings within the generated file.
string_table: StringTable = .{},
/// Map for storing anonymous declarations. Each anonymous decl maps to its Atom's index.
anon_decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, Atom.Index) = .{},
/// List of atom indexes of functions that are generated by the backend.
synthetic_functions: std.ArrayListUnmanaged(Atom.Index) = .{},
/// Represents the symbol index of the error name table
/// When this is `null`, no code references an error using runtime `@errorName`.
/// During initializion, a symbol with corresponding atom will be created that is
/// used to perform relocations to the pointer of this table.
/// The actual table is populated during `flush`.
error_table_symbol: Symbol.Index = .null,
/// Atom index of the table of symbol names. This is stored so we can clean up the atom.
error_names_atom: Atom.Index = .null,
/// Amount of functions in the `import` sections.
imported_functions_count: u32 = 0,
/// Amount of globals in the `import` section.
imported_globals_count: u32 = 0,
/// Symbol index representing the stack pointer. This will be set upon initializion
/// of a new `ZigObject`. Codegen will make calls into this to create relocations for
/// this symbol each time the stack pointer is moved.
stack_pointer_sym: Symbol.Index,
/// Debug information for the Zig module.
dwarf: ?Dwarf = null,
// Debug section atoms. These are only set when the current compilation
// unit contains Zig code. The lifetime of these atoms are extended
// until the end of the compiler's lifetime. Meaning they're not freed
// during `flush()` in incremental-mode.
debug_info_atom: ?Atom.Index = null,
debug_line_atom: ?Atom.Index = null,
debug_loc_atom: ?Atom.Index = null,
debug_ranges_atom: ?Atom.Index = null,
debug_abbrev_atom: ?Atom.Index = null,
debug_str_atom: ?Atom.Index = null,
debug_pubnames_atom: ?Atom.Index = null,
debug_pubtypes_atom: ?Atom.Index = null,
/// The index of the segment representing the custom '.debug_info' section.
debug_info_index: ?u32 = null,
/// The index of the segment representing the custom '.debug_line' section.
debug_line_index: ?u32 = null,
/// The index of the segment representing the custom '.debug_loc' section.
debug_loc_index: ?u32 = null,
/// The index of the segment representing the custom '.debug_ranges' section.
debug_ranges_index: ?u32 = null,
/// The index of the segment representing the custom '.debug_pubnames' section.
debug_pubnames_index: ?u32 = null,
/// The index of the segment representing the custom '.debug_pubtypes' section.
debug_pubtypes_index: ?u32 = null,
/// The index of the segment representing the custom '.debug_pubtypes' section.
debug_str_index: ?u32 = null,
/// The index of the segment representing the custom '.debug_pubtypes' section.
debug_abbrev_index: ?u32 = null,

const DeclInfo = struct {
    atom: Atom.Index = .null,
    exports: std.ArrayListUnmanaged(Symbol.Index) = .{},

    fn @"export"(di: DeclInfo, zig_object: *const ZigObject, name: []const u8) ?Symbol.Index {
        for (di.exports.items) |sym_index| {
            const sym_name_index = zig_object.symbol(sym_index).name;
            const sym_name = zig_object.string_table.getAssumeExists(sym_name_index);
            if (std.mem.eql(u8, name, sym_name)) {
                return sym_index;
            }
        }
        return null;
    }

    fn appendExport(di: *DeclInfo, gpa: std.mem.Allocator, sym_index: Symbol.Index) !void {
        return di.exports.append(gpa, sym_index);
    }

    fn deleteExport(di: *DeclInfo, sym_index: Symbol.Index) void {
        for (di.exports.items, 0..) |idx, index| {
            if (idx == sym_index) {
                _ = di.exports.swapRemove(index);
                return;
            }
        }
        unreachable; // invalid sym_index
    }
};

/// Initializes the `ZigObject` with initial symbols.
pub fn init(zig_object: *ZigObject, wasm_file: *Wasm) !void {
    // Initialize an undefined global with the name __stack_pointer. Codegen will use
    // this to generate relocations when moving the stack pointer. This symbol will be
    // resolved automatically by the final linking stage.
    try zig_object.createStackPointer(wasm_file);

    // TODO: Initialize debug information when we reimplement Dwarf support.
}

fn createStackPointer(zig_object: *ZigObject, wasm_file: *Wasm) !void {
    const gpa = wasm_file.base.comp.gpa;
    const sym_index = try zig_object.getGlobalSymbol(gpa, "__stack_pointer");
    const sym = zig_object.symbol(sym_index);
    sym.index = zig_object.imported_globals_count;
    sym.tag = .global;
    const is_wasm32 = wasm_file.base.comp.root_mod.resolved_target.result.cpu.arch == .wasm32;
    try zig_object.imports.putNoClobber(gpa, sym_index, .{
        .name = sym.name,
        .module_name = try zig_object.string_table.insert(gpa, wasm_file.host_name),
        .kind = .{ .global = .{ .valtype = if (is_wasm32) .i32 else .i64, .mutable = true } },
    });
    zig_object.imported_globals_count += 1;
    zig_object.stack_pointer_sym = sym_index;
}

fn symbol(zig_object: *const ZigObject, index: Symbol.Index) *Symbol {
    return &zig_object.symbols.items[@intFromEnum(index)];
}

/// Frees and invalidates all memory of the incrementally compiled Zig module.
/// It is illegal behavior to access the `ZigObject` after calling `deinit`.
pub fn deinit(zig_object: *ZigObject, wasm_file: *Wasm) void {
    const gpa = wasm_file.base.comp.gpa;
    for (zig_object.segment_info.items) |segment_info| {
        gpa.free(segment_info.name);
    }

    {
        var it = zig_object.decls_map.valueIterator();
        while (it.next()) |decl_info| {
            const atom = wasm_file.getAtomPtr(decl_info.atom);
            for (atom.locals.items) |local_index| {
                const local_atom = wasm_file.getAtomPtr(local_index);
                local_atom.deinit(gpa);
            }
            atom.deinit(gpa);
            decl_info.exports.deinit(gpa);
        }
    }
    {
        for (zig_object.anon_decls.values()) |atom_index| {
            const atom = wasm_file.getAtomPtr(atom_index);
            for (atom.locals.items) |local_index| {
                const local_atom = wasm_file.getAtomPtr(local_index);
                local_atom.deinit(gpa);
            }
            atom.deinit(gpa);
        }
    }
    if (zig_object.findGlobalSymbol("__zig_errors_len")) |sym_index| {
        const atom_index = wasm_file.symbol_atom.get(.{ .file = zig_object.index, .index = sym_index }).?;
        wasm_file.getAtomPtr(atom_index).deinit(gpa);
    }
    if (wasm_file.symbol_atom.get(.{ .file = zig_object.index, .index = zig_object.error_table_symbol })) |atom_index| {
        const atom = wasm_file.getAtomPtr(atom_index);
        atom.deinit(gpa);
    }
    for (zig_object.synthetic_functions.items) |atom_index| {
        const atom = wasm_file.getAtomPtr(atom_index);
        atom.deinit(gpa);
    }
    zig_object.synthetic_functions.deinit(gpa);
    for (zig_object.func_types.items) |*ty| {
        ty.deinit(gpa);
    }
    if (zig_object.error_names_atom != .null) {
        const atom = wasm_file.getAtomPtr(zig_object.error_names_atom);
        atom.deinit(gpa);
    }
    zig_object.global_syms.deinit(gpa);
    zig_object.func_types.deinit(gpa);
    zig_object.atom_types.deinit(gpa);
    zig_object.functions.deinit(gpa);
    zig_object.imports.deinit(gpa);
    zig_object.decls_map.deinit(gpa);
    zig_object.anon_decls.deinit(gpa);
    zig_object.symbols.deinit(gpa);
    zig_object.symbols_free_list.deinit(gpa);
    zig_object.segment_info.deinit(gpa);
    zig_object.segment_free_list.deinit(gpa);

    zig_object.string_table.deinit(gpa);
    if (zig_object.dwarf) |*dwarf| {
        dwarf.deinit();
    }
    gpa.free(zig_object.path);
    zig_object.* = undefined;
}

/// Allocates a new symbol and returns its index.
/// Will re-use slots when a symbol was freed at an earlier stage.
pub fn allocateSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator) !Symbol.Index {
    try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
    const sym: Symbol = .{
        .name = std.math.maxInt(u32), // will be set after updateDecl as well as during atom creation for decls
        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
        .tag = .undefined, // will be set after updateDecl
        .index = std.math.maxInt(u32), // will be set during atom parsing
        .virtual_address = std.math.maxInt(u32), // will be set during atom allocation
    };
    if (zig_object.symbols_free_list.popOrNull()) |index| {
        zig_object.symbols.items[@intFromEnum(index)] = sym;
        return index;
    }
    const index: Symbol.Index = @enumFromInt(zig_object.symbols.items.len);
    zig_object.symbols.appendAssumeCapacity(sym);
    return index;
}

// Generate code for the Decl, storing it in memory to be later written to
// the file on flush().
pub fn updateDecl(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    mod: *Module,
    decl_index: InternPool.DeclIndex,
) !void {
    const decl = mod.declPtr(decl_index);
    if (decl.val.getFunction(mod)) |_| {
        return;
    } else if (decl.val.getExternFunc(mod)) |_| {
        return;
    }

    const gpa = wasm_file.base.comp.gpa;
    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
    const atom = wasm_file.getAtomPtr(atom_index);
    atom.clear();

    if (decl.isExtern(mod)) {
        const variable = decl.getOwnedVariable(mod).?;
        const name = decl.name.toSlice(&mod.intern_pool);
        const lib_name = variable.lib_name.toSlice(&mod.intern_pool);
        return zig_object.addOrUpdateImport(wasm_file, name, atom.sym_index, lib_name, null);
    }
    const val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;

    var code_writer = std.ArrayList(u8).init(gpa);
    defer code_writer.deinit();

    const res = try codegen.generateSymbol(
        &wasm_file.base,
        decl.srcLoc(mod),
        val,
        &code_writer,
        .none,
        .{ .parent_atom_index = @intFromEnum(atom.sym_index) },
    );

    const code = switch (res) {
        .ok => code_writer.items,
        .fail => |em| {
            decl.analysis = .codegen_failure;
            try mod.failed_decls.put(mod.gpa, decl_index, em);
            return;
        },
    };

    return zig_object.finishUpdateDecl(wasm_file, decl_index, code);
}

pub fn updateFunc(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    mod: *Module,
    func_index: InternPool.Index,
    air: Air,
    liveness: Liveness,
) !void {
    const gpa = wasm_file.base.comp.gpa;
    const func = mod.funcInfo(func_index);
    const decl_index = func.owner_decl;
    const decl = mod.declPtr(decl_index);
    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
    const atom = wasm_file.getAtomPtr(atom_index);
    atom.clear();

    var code_writer = std.ArrayList(u8).init(gpa);
    defer code_writer.deinit();
    const result = try codegen.generateFunction(
        &wasm_file.base,
        decl.srcLoc(mod),
        func_index,
        air,
        liveness,
        &code_writer,
        .none,
    );

    const code = switch (result) {
        .ok => code_writer.items,
        .fail => |em| {
            decl.analysis = .codegen_failure;
            try mod.failed_decls.put(mod.gpa, decl_index, em);
            return;
        },
    };

    return zig_object.finishUpdateDecl(wasm_file, decl_index, code);
}

fn finishUpdateDecl(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    decl_index: InternPool.DeclIndex,
    code: []const u8,
) !void {
    const gpa = wasm_file.base.comp.gpa;
    const mod = wasm_file.base.comp.module.?;
    const decl = mod.declPtr(decl_index);
    const decl_info = zig_object.decls_map.get(decl_index).?;
    const atom_index = decl_info.atom;
    const atom = wasm_file.getAtomPtr(atom_index);
    const sym = zig_object.symbol(atom.sym_index);
    const full_name = try decl.fullyQualifiedName(mod);
    sym.name = try zig_object.string_table.insert(gpa, full_name.toSlice(&mod.intern_pool));
    try atom.code.appendSlice(gpa, code);
    atom.size = @intCast(code.len);

    switch (decl.typeOf(mod).zigTypeTag(mod)) {
        .Fn => {
            sym.index = try zig_object.appendFunction(gpa, .{ .type_index = zig_object.atom_types.get(atom_index).? });
            sym.tag = .function;
        },
        else => {
            const segment_name: []const u8 = if (decl.getOwnedVariable(mod)) |variable| name: {
                if (variable.is_const) {
                    break :name ".rodata.";
                } else if (Value.fromInterned(variable.init).isUndefDeep(mod)) {
                    const decl_namespace = mod.namespacePtr(decl.src_namespace);
                    const optimize_mode = decl_namespace.file_scope.mod.optimize_mode;
                    const is_initialized = switch (optimize_mode) {
                        .Debug, .ReleaseSafe => true,
                        .ReleaseFast, .ReleaseSmall => false,
                    };
                    if (is_initialized) {
                        break :name ".data.";
                    }
                    break :name ".bss.";
                }
                // when the decl is all zeroes, we store the atom in the bss segment,
                // in all other cases it will be in the data segment.
                for (atom.code.items) |byte| {
                    if (byte != 0) break :name ".data.";
                }
                break :name ".bss.";
            } else ".rodata.";
            if ((wasm_file.base.isObject() or wasm_file.base.comp.config.import_memory) and
                std.mem.startsWith(u8, segment_name, ".bss"))
            {
                @memset(atom.code.items, 0);
            }
            // Will be freed upon freeing of decl or after cleanup of Wasm binary.
            const full_segment_name = try std.mem.concat(gpa, u8, &.{
                segment_name,
                full_name.toSlice(&mod.intern_pool),
            });
            errdefer gpa.free(full_segment_name);
            sym.tag = .data;
            sym.index = try zig_object.createDataSegment(gpa, full_segment_name, decl.alignment);
        },
    }
    if (code.len == 0) return;
    atom.alignment = decl.getAlignment(mod);
}

/// Creates and initializes a new segment in the 'Data' section.
/// Reuses free slots in the list of segments and returns the index.
fn createDataSegment(
    zig_object: *ZigObject,
    gpa: std.mem.Allocator,
    name: []const u8,
    alignment: InternPool.Alignment,
) !u32 {
    const segment_index: u32 = if (zig_object.segment_free_list.popOrNull()) |index|
        index
    else index: {
        const idx: u32 = @intCast(zig_object.segment_info.items.len);
        _ = try zig_object.segment_info.addOne(gpa);
        break :index idx;
    };
    zig_object.segment_info.items[segment_index] = .{
        .alignment = alignment,
        .flags = 0,
        .name = name,
    };
    return segment_index;
}

/// For a given `InternPool.DeclIndex` returns its corresponding `Atom.Index`.
/// When the index was not found, a new `Atom` will be created, and its index will be returned.
/// The newly created Atom is empty with default fields as specified by `Atom.empty`.
pub fn getOrCreateAtomForDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) !Atom.Index {
    const gpa = wasm_file.base.comp.gpa;
    const gop = try zig_object.decls_map.getOrPut(gpa, decl_index);
    if (!gop.found_existing) {
        const sym_index = try zig_object.allocateSymbol(gpa);
        gop.value_ptr.* = .{ .atom = try wasm_file.createAtom(sym_index, zig_object.index) };
        const mod = wasm_file.base.comp.module.?;
        const decl = mod.declPtr(decl_index);
        const full_name = try decl.fullyQualifiedName(mod);
        const sym = zig_object.symbol(sym_index);
        sym.name = try zig_object.string_table.insert(gpa, full_name.toSlice(&mod.intern_pool));
    }
    return gop.value_ptr.atom;
}

pub fn lowerAnonDecl(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    decl_val: InternPool.Index,
    explicit_alignment: InternPool.Alignment,
    src_loc: Module.SrcLoc,
) !codegen.Result {
    const gpa = wasm_file.base.comp.gpa;
    const gop = try zig_object.anon_decls.getOrPut(gpa, decl_val);
    if (!gop.found_existing) {
        var name_buf: [32]u8 = undefined;
        const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
            @intFromEnum(decl_val),
        }) catch unreachable;

        switch (try zig_object.lowerConst(wasm_file, name, Value.fromInterned(decl_val), src_loc)) {
            .ok => |atom_index| zig_object.anon_decls.values()[gop.index] = atom_index,
            .fail => |em| return .{ .fail = em },
        }
    }

    const atom = wasm_file.getAtomPtr(zig_object.anon_decls.values()[gop.index]);
    atom.alignment = switch (atom.alignment) {
        .none => explicit_alignment,
        else => switch (explicit_alignment) {
            .none => atom.alignment,
            else => atom.alignment.maxStrict(explicit_alignment),
        },
    };
    return .ok;
}

/// Lowers a constant typed value to a local symbol and atom.
/// Returns the symbol index of the local
/// The given `decl` is the parent decl whom owns the constant.
pub fn lowerUnnamedConst(zig_object: *ZigObject, wasm_file: *Wasm, val: Value, decl_index: InternPool.DeclIndex) !u32 {
    const gpa = wasm_file.base.comp.gpa;
    const mod = wasm_file.base.comp.module.?;
    std.debug.assert(val.typeOf(mod).zigTypeTag(mod) != .Fn); // cannot create local symbols for functions
    const decl = mod.declPtr(decl_index);

    const parent_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
    const parent_atom = wasm_file.getAtom(parent_atom_index);
    const local_index = parent_atom.locals.items.len;
    const fqn = try decl.fullyQualifiedName(mod);
    const name = try std.fmt.allocPrintZ(gpa, "__unnamed_{}_{d}", .{
        fqn.fmt(&mod.intern_pool), local_index,
    });
    defer gpa.free(name);

    switch (try zig_object.lowerConst(wasm_file, name, val, decl.srcLoc(mod))) {
        .ok => |atom_index| {
            try wasm_file.getAtomPtr(parent_atom_index).locals.append(gpa, atom_index);
            return @intFromEnum(wasm_file.getAtom(atom_index).sym_index);
        },
        .fail => |em| {
            decl.analysis = .codegen_failure;
            try mod.failed_decls.put(mod.gpa, decl_index, em);
            return error.CodegenFail;
        },
    }
}

const LowerConstResult = union(enum) {
    ok: Atom.Index,
    fail: *Module.ErrorMsg,
};

fn lowerConst(zig_object: *ZigObject, wasm_file: *Wasm, name: []const u8, val: Value, src_loc: Module.SrcLoc) !LowerConstResult {
    const gpa = wasm_file.base.comp.gpa;
    const mod = wasm_file.base.comp.module.?;

    const ty = val.typeOf(mod);

    // Create and initialize a new local symbol and atom
    const sym_index = try zig_object.allocateSymbol(gpa);
    const atom_index = try wasm_file.createAtom(sym_index, zig_object.index);
    var value_bytes = std.ArrayList(u8).init(gpa);
    defer value_bytes.deinit();

    const code = code: {
        const atom = wasm_file.getAtomPtr(atom_index);
        atom.alignment = ty.abiAlignment(mod);
        const segment_name = try std.mem.concat(gpa, u8, &.{ ".rodata.", name });
        errdefer gpa.free(segment_name);
        zig_object.symbol(sym_index).* = .{
            .name = try zig_object.string_table.insert(gpa, name),
            .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
            .tag = .data,
            .index = try zig_object.createDataSegment(
                gpa,
                segment_name,
                ty.abiAlignment(mod),
            ),
            .virtual_address = undefined,
        };

        const result = try codegen.generateSymbol(
            &wasm_file.base,
            src_loc,
            val,
            &value_bytes,
            .none,
            .{
                .parent_atom_index = @intFromEnum(atom.sym_index),
            },
        );
        break :code switch (result) {
            .ok => value_bytes.items,
            .fail => |em| {
                return .{ .fail = em };
            },
        };
    };

    const atom = wasm_file.getAtomPtr(atom_index);
    atom.size = @intCast(code.len);
    try atom.code.appendSlice(gpa, code);
    return .{ .ok = atom_index };
}

/// Returns the symbol index of the error name table.
///
/// When the symbol does not yet exist, it will create a new one instead.
pub fn getErrorTableSymbol(zig_object: *ZigObject, wasm_file: *Wasm) !Symbol.Index {
    if (zig_object.error_table_symbol != .null) {
        return zig_object.error_table_symbol;
    }

    // no error was referenced yet, so create a new symbol and atom for it
    // and then return said symbol's index. The final table will be populated
    // during `flush` when we know all possible error names.
    const gpa = wasm_file.base.comp.gpa;
    const sym_index = try zig_object.allocateSymbol(gpa);
    const atom_index = try wasm_file.createAtom(sym_index, zig_object.index);
    const atom = wasm_file.getAtomPtr(atom_index);
    const slice_ty = Type.slice_const_u8_sentinel_0;
    const mod = wasm_file.base.comp.module.?;
    atom.alignment = slice_ty.abiAlignment(mod);

    const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_name_table");
    const segment_name = try gpa.dupe(u8, ".rodata.__zig_err_name_table");
    const sym = zig_object.symbol(sym_index);
    sym.* = .{
        .name = sym_name,
        .tag = .data,
        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
        .index = try zig_object.createDataSegment(gpa, segment_name, atom.alignment),
        .virtual_address = undefined,
    };

    log.debug("Error name table was created with symbol index: ({d})", .{@intFromEnum(sym_index)});
    zig_object.error_table_symbol = sym_index;
    return sym_index;
}

/// Populates the error name table, when `error_table_symbol` is not null.
///
/// This creates a table that consists of pointers and length to each error name.
/// The table is what is being pointed to within the runtime bodies that are generated.
fn populateErrorNameTable(zig_object: *ZigObject, wasm_file: *Wasm) !void {
    if (zig_object.error_table_symbol == .null) return;
    const gpa = wasm_file.base.comp.gpa;
    const atom_index = wasm_file.symbol_atom.get(.{ .file = zig_object.index, .index = zig_object.error_table_symbol }).?;

    // Rather than creating a symbol for each individual error name,
    // we create a symbol for the entire region of error names. We then calculate
    // the pointers into the list using addends which are appended to the relocation.
    const names_sym_index = try zig_object.allocateSymbol(gpa);
    const names_atom_index = try wasm_file.createAtom(names_sym_index, zig_object.index);
    const names_atom = wasm_file.getAtomPtr(names_atom_index);
    names_atom.alignment = .@"1";
    const sym_name = try zig_object.string_table.insert(gpa, "__zig_err_names");
    const segment_name = try gpa.dupe(u8, ".rodata.__zig_err_names");
    const names_symbol = zig_object.symbol(names_sym_index);
    names_symbol.* = .{
        .name = sym_name,
        .tag = .data,
        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
        .index = try zig_object.createDataSegment(gpa, segment_name, names_atom.alignment),
        .virtual_address = undefined,
    };

    log.debug("Populating error names", .{});

    // Addend for each relocation to the table
    var addend: u32 = 0;
    const mod = wasm_file.base.comp.module.?;
    for (mod.global_error_set.keys()) |error_name| {
        const atom = wasm_file.getAtomPtr(atom_index);

        const error_name_slice = error_name.toSlice(&mod.intern_pool);
        const len: u32 = @intCast(error_name_slice.len + 1); // names are 0-terminated

        const slice_ty = Type.slice_const_u8_sentinel_0;
        const offset = @as(u32, @intCast(atom.code.items.len));
        // first we create the data for the slice of the name
        try atom.code.appendNTimes(gpa, 0, 4); // ptr to name, will be relocated
        try atom.code.writer(gpa).writeInt(u32, len - 1, .little);
        // create relocation to the error name
        try atom.relocs.append(gpa, .{
            .index = @intFromEnum(names_atom.sym_index),
            .relocation_type = .R_WASM_MEMORY_ADDR_I32,
            .offset = offset,
            .addend = @intCast(addend),
        });
        atom.size += @intCast(slice_ty.abiSize(mod));
        addend += len;

        // as we updated the error name table, we now store the actual name within the names atom
        try names_atom.code.ensureUnusedCapacity(gpa, len);
        names_atom.code.appendSliceAssumeCapacity(error_name_slice[0..len]);

        log.debug("Populated error name: '{}'", .{error_name.fmt(&mod.intern_pool)});
    }
    names_atom.size = addend;
    zig_object.error_names_atom = names_atom_index;
}

/// Either creates a new import, or updates one if existing.
/// When `type_index` is non-null, we assume an external function.
/// In all other cases, a data-symbol will be created instead.
pub fn addOrUpdateImport(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    /// Name of the import
    name: []const u8,
    /// Symbol index that is external
    symbol_index: Symbol.Index,
    /// Optional library name (i.e. `extern "c" fn foo() void`
    lib_name: ?[:0]const u8,
    /// The index of the type that represents the function signature
    /// when the extern is a function. When this is null, a data-symbol
    /// is asserted instead.
    type_index: ?u32,
) !void {
    const gpa = wasm_file.base.comp.gpa;
    std.debug.assert(symbol_index != .null);
    // For the import name, we use the decl's name, rather than the fully qualified name
    // Also mangle the name when the lib name is set and not equal to "C" so imports with the same
    // name but different module can be resolved correctly.
    const mangle_name = lib_name != null and
        !std.mem.eql(u8, lib_name.?, "c");
    const full_name = if (mangle_name) full_name: {
        break :full_name try std.fmt.allocPrint(gpa, "{s}|{s}", .{ name, lib_name.? });
    } else name;
    defer if (mangle_name) gpa.free(full_name);

    const decl_name_index = try zig_object.string_table.insert(gpa, full_name);
    const sym: *Symbol = &zig_object.symbols.items[@intFromEnum(symbol_index)];
    sym.setUndefined(true);
    sym.setGlobal(true);
    sym.name = decl_name_index;
    if (mangle_name) {
        // we specified a specific name for the symbol that does not match the import name
        sym.setFlag(.WASM_SYM_EXPLICIT_NAME);
    }

    if (type_index) |ty_index| {
        const gop = try zig_object.imports.getOrPut(gpa, symbol_index);
        const module_name = if (lib_name) |l_name| l_name else wasm_file.host_name;
        if (!gop.found_existing) {
            zig_object.imported_functions_count += 1;
        }
        gop.value_ptr.* = .{
            .module_name = try zig_object.string_table.insert(gpa, module_name),
            .name = try zig_object.string_table.insert(gpa, name),
            .kind = .{ .function = ty_index },
        };
        sym.tag = .function;
    } else {
        sym.tag = .data;
    }
}

/// Returns the symbol index from a symbol of which its flag is set global,
/// such as an exported or imported symbol.
/// If the symbol does not yet exist, creates a new one symbol instead
/// and then returns the index to it.
pub fn getGlobalSymbol(zig_object: *ZigObject, gpa: std.mem.Allocator, name: []const u8) !Symbol.Index {
    const name_index = try zig_object.string_table.insert(gpa, name);
    const gop = try zig_object.global_syms.getOrPut(gpa, name_index);
    if (gop.found_existing) {
        return gop.value_ptr.*;
    }

    var sym: Symbol = .{
        .name = name_index,
        .flags = 0,
        .index = undefined, // index to type will be set after merging symbols
        .tag = .function,
        .virtual_address = std.math.maxInt(u32),
    };
    sym.setGlobal(true);
    sym.setUndefined(true);

    const sym_index = if (zig_object.symbols_free_list.popOrNull()) |index| index else blk: {
        const index: Symbol.Index = @enumFromInt(zig_object.symbols.items.len);
        try zig_object.symbols.ensureUnusedCapacity(gpa, 1);
        zig_object.symbols.items.len += 1;
        break :blk index;
    };
    zig_object.symbol(sym_index).* = sym;
    gop.value_ptr.* = sym_index;
    return sym_index;
}

/// For a given decl, find the given symbol index's atom, and create a relocation for the type.
/// Returns the given pointer address
pub fn getDeclVAddr(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    decl_index: InternPool.DeclIndex,
    reloc_info: link.File.RelocInfo,
) !u64 {
    const target = wasm_file.base.comp.root_mod.resolved_target.result;
    const gpa = wasm_file.base.comp.gpa;
    const mod = wasm_file.base.comp.module.?;
    const decl = mod.declPtr(decl_index);

    const target_atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
    const target_symbol_index = @intFromEnum(wasm_file.getAtom(target_atom_index).sym_index);

    std.debug.assert(reloc_info.parent_atom_index != 0);
    const atom_index = wasm_file.symbol_atom.get(.{ .file = zig_object.index, .index = @enumFromInt(reloc_info.parent_atom_index) }).?;
    const atom = wasm_file.getAtomPtr(atom_index);
    const is_wasm32 = target.cpu.arch == .wasm32;
    if (decl.typeOf(mod).zigTypeTag(mod) == .Fn) {
        std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations
        try atom.relocs.append(gpa, .{
            .index = target_symbol_index,
            .offset = @intCast(reloc_info.offset),
            .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
        });
    } else {
        try atom.relocs.append(gpa, .{
            .index = target_symbol_index,
            .offset = @intCast(reloc_info.offset),
            .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
            .addend = @intCast(reloc_info.addend),
        });
    }

    // we do not know the final address at this point,
    // as atom allocation will determine the address and relocations
    // will calculate and rewrite this. Therefore, we simply return the symbol index
    // that was targeted.
    return target_symbol_index;
}

pub fn getAnonDeclVAddr(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    decl_val: InternPool.Index,
    reloc_info: link.File.RelocInfo,
) !u64 {
    const gpa = wasm_file.base.comp.gpa;
    const target = wasm_file.base.comp.root_mod.resolved_target.result;
    const atom_index = zig_object.anon_decls.get(decl_val).?;
    const target_symbol_index = @intFromEnum(wasm_file.getAtom(atom_index).sym_index);

    const parent_atom_index = wasm_file.symbol_atom.get(.{ .file = zig_object.index, .index = @enumFromInt(reloc_info.parent_atom_index) }).?;
    const parent_atom = wasm_file.getAtomPtr(parent_atom_index);
    const is_wasm32 = target.cpu.arch == .wasm32;
    const mod = wasm_file.base.comp.module.?;
    const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
    if (ty.zigTypeTag(mod) == .Fn) {
        std.debug.assert(reloc_info.addend == 0); // addend not allowed for function relocations
        try parent_atom.relocs.append(gpa, .{
            .index = target_symbol_index,
            .offset = @intCast(reloc_info.offset),
            .relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
        });
    } else {
        try parent_atom.relocs.append(gpa, .{
            .index = target_symbol_index,
            .offset = @intCast(reloc_info.offset),
            .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
            .addend = @intCast(reloc_info.addend),
        });
    }

    // we do not know the final address at this point,
    // as atom allocation will determine the address and relocations
    // will calculate and rewrite this. Therefore, we simply return the symbol index
    // that was targeted.
    return target_symbol_index;
}

pub fn deleteDeclExport(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    decl_index: InternPool.DeclIndex,
    name: InternPool.NullTerminatedString,
) void {
    const mod = wasm_file.base.comp.module.?;
    const decl_info = zig_object.decls_map.getPtr(decl_index) orelse return;
    if (decl_info.@"export"(zig_object, name.toSlice(&mod.intern_pool))) |sym_index| {
        const sym = zig_object.symbol(sym_index);
        decl_info.deleteExport(sym_index);
        std.debug.assert(zig_object.global_syms.remove(sym.name));
        std.debug.assert(wasm_file.symbol_atom.remove(.{ .file = zig_object.index, .index = sym_index }));
        zig_object.symbols_free_list.append(wasm_file.base.comp.gpa, sym_index) catch {};
        sym.tag = .dead;
    }
}

pub fn updateExports(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    mod: *Module,
    exported: Module.Exported,
    exports: []const *Module.Export,
) !void {
    const decl_index = switch (exported) {
        .decl_index => |i| i,
        .value => |val| {
            _ = val;
            @panic("TODO: implement Wasm linker code for exporting a constant value");
        },
    };
    const decl = mod.declPtr(decl_index);
    const atom_index = try zig_object.getOrCreateAtomForDecl(wasm_file, decl_index);
    const decl_info = zig_object.decls_map.getPtr(decl_index).?;
    const atom = wasm_file.getAtom(atom_index);
    const atom_sym = atom.symbolLoc().getSymbol(wasm_file).*;
    const gpa = mod.gpa;
    log.debug("Updating exports for decl '{}'", .{decl.name.fmt(&mod.intern_pool)});

    for (exports) |exp| {
        if (exp.opts.section.toSlice(&mod.intern_pool)) |section| {
            try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
                gpa,
                decl.srcLoc(mod),
                "Unimplemented: ExportOptions.section '{s}'",
                .{section},
            ));
            continue;
        }

        const export_string = exp.opts.name.toSlice(&mod.intern_pool);
        const sym_index = if (decl_info.@"export"(zig_object, export_string)) |idx| idx else index: {
            const sym_index = try zig_object.allocateSymbol(gpa);
            try decl_info.appendExport(gpa, sym_index);
            break :index sym_index;
        };

        const export_name = try zig_object.string_table.insert(gpa, export_string);
        const sym = zig_object.symbol(sym_index);
        sym.setGlobal(true);
        sym.setUndefined(false);
        sym.index = atom_sym.index;
        sym.tag = atom_sym.tag;
        sym.name = export_name;

        switch (exp.opts.linkage) {
            .internal => {
                sym.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
            },
            .weak => {
                sym.setFlag(.WASM_SYM_BINDING_WEAK);
            },
            .strong => {}, // symbols are strong by default
            .link_once => {
                try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
                    gpa,
                    decl.srcLoc(mod),
                    "Unimplemented: LinkOnce",
                    .{},
                ));
                continue;
            },
        }
        if (exp.opts.visibility == .hidden) {
            sym.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
        }
        log.debug("  with name '{s}' - {}", .{ export_string, sym });
        try zig_object.global_syms.put(gpa, export_name, sym_index);
        try wasm_file.symbol_atom.put(gpa, .{ .file = zig_object.index, .index = sym_index }, atom_index);
    }
}

pub fn freeDecl(zig_object: *ZigObject, wasm_file: *Wasm, decl_index: InternPool.DeclIndex) void {
    const gpa = wasm_file.base.comp.gpa;
    const mod = wasm_file.base.comp.module.?;
    const decl = mod.declPtr(decl_index);
    const decl_info = zig_object.decls_map.getPtr(decl_index).?;
    const atom_index = decl_info.atom;
    const atom = wasm_file.getAtomPtr(atom_index);
    zig_object.symbols_free_list.append(gpa, atom.sym_index) catch {};
    for (decl_info.exports.items) |exp_sym_index| {
        const exp_sym = zig_object.symbol(exp_sym_index);
        exp_sym.tag = .dead;
        zig_object.symbols_free_list.append(exp_sym_index) catch {};
    }
    decl_info.exports.deinit(gpa);
    std.debug.assert(zig_object.decls_map.remove(decl_index));
    const sym = &zig_object.symbols.items[atom.sym_index];
    for (atom.locals.items) |local_atom_index| {
        const local_atom = wasm_file.getAtom(local_atom_index);
        const local_symbol = &zig_object.symbols.items[local_atom.sym_index];
        std.debug.assert(local_symbol.tag == .data);
        zig_object.symbols_free_list.append(gpa, local_atom.sym_index) catch {};
        std.debug.assert(wasm_file.symbol_atom.remove(local_atom.symbolLoc()));
        local_symbol.tag = .dead; // also for any local symbol
        const segment = &zig_object.segment_info.items[local_atom.sym_index];
        gpa.free(segment.name);
        segment.name = &.{}; // Ensure no accidental double free
    }

    if (decl.isExtern(mod)) {
        std.debug.assert(zig_object.imports.remove(atom.sym_index));
    }
    std.debug.assert(wasm_file.symbol_atom.remove(atom.symbolLoc()));

    // if (wasm.dwarf) |*dwarf| {
    //     dwarf.freeDecl(decl_index);
    // }

    atom.prev = null;
    sym.tag = .dead;
    if (sym.isGlobal()) {
        std.debug.assert(zig_object.global_syms.remove(atom.sym_index));
    }
    switch (decl.typeOf(mod).zigTypeTag(mod)) {
        .Fn => {
            zig_object.functions_free_list.append(gpa, sym.index) catch {};
            std.debug.assert(zig_object.atom_types.remove(atom_index));
        },
        else => {
            zig_object.segment_free_list.append(gpa, sym.index) catch {};
            const segment = &zig_object.segment_info.items[sym.index];
            gpa.free(segment.name);
            segment.name = &.{}; // Prevent accidental double free
        },
    }
}

fn getTypeIndex(zig_object: *const ZigObject, func_type: std.wasm.Type) ?u32 {
    var index: u32 = 0;
    while (index < zig_object.func_types.items.len) : (index += 1) {
        if (zig_object.func_types.items[index].eql(func_type)) return index;
    }
    return null;
}

/// Searches for a matching function signature. When no matching signature is found,
/// a new entry will be made. The value returned is the index of the type within `wasm.func_types`.
pub fn putOrGetFuncType(zig_object: *ZigObject, gpa: std.mem.Allocator, func_type: std.wasm.Type) !u32 {
    if (zig_object.getTypeIndex(func_type)) |index| {
        return index;
    }

    // functype does not exist.
    const index: u32 = @intCast(zig_object.func_types.items.len);
    const params = try gpa.dupe(std.wasm.Valtype, func_type.params);
    errdefer gpa.free(params);
    const returns = try gpa.dupe(std.wasm.Valtype, func_type.returns);
    errdefer gpa.free(returns);
    try zig_object.func_types.append(gpa, .{
        .params = params,
        .returns = returns,
    });
    return index;
}

/// Generates an atom containing the global error set' size.
/// This will only be generated if the symbol exists.
fn setupErrorsLen(zig_object: *ZigObject, wasm_file: *Wasm) !void {
    const gpa = wasm_file.base.comp.gpa;
    const sym_index = zig_object.findGlobalSymbol("__zig_errors_len") orelse return;

    const errors_len = wasm_file.base.comp.module.?.global_error_set.count();
    // overwrite existing atom if it already exists (maybe the error set has increased)
    // if not, allcoate a new atom.
    const atom_index = if (wasm_file.symbol_atom.get(.{ .file = zig_object.index, .index = sym_index })) |index| blk: {
        const atom = wasm_file.getAtomPtr(index);
        atom.prev = .null;
        atom.deinit(gpa);
        break :blk index;
    } else idx: {
        // We found a call to __zig_errors_len so make the symbol a local symbol
        // and define it, so the final binary or resulting object file will not attempt
        // to resolve it.
        const sym = zig_object.symbol(sym_index);
        sym.setGlobal(false);
        sym.setUndefined(false);
        sym.tag = .data;
        const segment_name = try gpa.dupe(u8, ".rodata.__zig_errors_len");
        sym.index = try zig_object.createDataSegment(gpa, segment_name, .@"2");
        break :idx try wasm_file.createAtom(sym_index, zig_object.index);
    };

    const atom = wasm_file.getAtomPtr(atom_index);
    atom.code.clearRetainingCapacity();
    atom.sym_index = sym_index;
    atom.size = 2;
    atom.alignment = .@"2";
    try atom.code.writer(gpa).writeInt(u16, @intCast(errors_len), .little);
}

fn findGlobalSymbol(zig_object: *ZigObject, name: []const u8) ?Symbol.Index {
    const offset = zig_object.string_table.getOffset(name) orelse return null;
    return zig_object.global_syms.get(offset);
}

/// Initializes symbols and atoms for the debug sections
/// Initialization is only done when compiling Zig code.
/// When Zig is invoked as a linker instead, the atoms
/// and symbols come from the object files instead.
pub fn initDebugSections(zig_object: *ZigObject) !void {
    if (zig_object.dwarf == null) return; // not compiling Zig code, so no need to pre-initialize debug sections
    std.debug.assert(zig_object.debug_info_index == null);
    // this will create an Atom and set the index for us.
    zig_object.debug_info_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_info_index, ".debug_info");
    zig_object.debug_line_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_line_index, ".debug_line");
    zig_object.debug_loc_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_loc_index, ".debug_loc");
    zig_object.debug_abbrev_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_abbrev_index, ".debug_abbrev");
    zig_object.debug_ranges_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_ranges_index, ".debug_ranges");
    zig_object.debug_str_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_str_index, ".debug_str");
    zig_object.debug_pubnames_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_pubnames_index, ".debug_pubnames");
    zig_object.debug_pubtypes_atom = try zig_object.createDebugSectionForIndex(&zig_object.debug_pubtypes_index, ".debug_pubtypes");
}

/// From a given index variable, creates a new debug section.
/// This initializes the index, appends a new segment,
/// and finally, creates a managed `Atom`.
pub fn createDebugSectionForIndex(zig_object: *ZigObject, wasm_file: *Wasm, index: *?u32, name: []const u8) !Atom.Index {
    const gpa = wasm_file.base.comp.gpa;
    const new_index: u32 = @intCast(zig_object.segments.items.len);
    index.* = new_index;
    try zig_object.appendDummySegment();

    const sym_index = try zig_object.allocateSymbol(gpa);
    const atom_index = try wasm_file.createAtom(sym_index, zig_object.index);
    const atom = wasm_file.getAtomPtr(atom_index);
    zig_object.symbols.items[sym_index] = .{
        .tag = .section,
        .name = try zig_object.string_table.put(gpa, name),
        .index = 0,
        .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
    };

    atom.alignment = .@"1"; // debug sections are always 1-byte-aligned
    return atom_index;
}

pub fn updateDeclLineNumber(zig_object: *ZigObject, mod: *Module, decl_index: InternPool.DeclIndex) !void {
    if (zig_object.dwarf) |*dw| {
        const decl = mod.declPtr(decl_index);
        const decl_name = try decl.fullyQualifiedName(mod);

        log.debug("updateDeclLineNumber {}{*}", .{ decl_name.fmt(&mod.intern_pool), decl });
        try dw.updateDeclLineNumber(mod, decl_index);
    }
}

/// Allocates debug atoms into their respective debug sections
/// to merge them with maybe-existing debug atoms from object files.
fn allocateDebugAtoms(zig_object: *ZigObject) !void {
    if (zig_object.dwarf == null) return;

    const allocAtom = struct {
        fn f(ctx: *ZigObject, maybe_index: *?u32, atom_index: Atom.Index) !void {
            const index = maybe_index.* orelse idx: {
                const index = @as(u32, @intCast(ctx.segments.items.len));
                try ctx.appendDummySegment();
                maybe_index.* = index;
                break :idx index;
            };
            const atom = ctx.getAtomPtr(atom_index);
            atom.size = @as(u32, @intCast(atom.code.items.len));
            ctx.symbols.items[atom.sym_index].index = index;
            try ctx.appendAtomAtIndex(index, atom_index);
        }
    }.f;

    try allocAtom(zig_object, &zig_object.debug_info_index, zig_object.debug_info_atom.?);
    try allocAtom(zig_object, &zig_object.debug_line_index, zig_object.debug_line_atom.?);
    try allocAtom(zig_object, &zig_object.debug_loc_index, zig_object.debug_loc_atom.?);
    try allocAtom(zig_object, &zig_object.debug_str_index, zig_object.debug_str_atom.?);
    try allocAtom(zig_object, &zig_object.debug_ranges_index, zig_object.debug_ranges_atom.?);
    try allocAtom(zig_object, &zig_object.debug_abbrev_index, zig_object.debug_abbrev_atom.?);
    try allocAtom(zig_object, &zig_object.debug_pubnames_index, zig_object.debug_pubnames_atom.?);
    try allocAtom(zig_object, &zig_object.debug_pubtypes_index, zig_object.debug_pubtypes_atom.?);
}

/// For the given `decl_index`, stores the corresponding type representing the function signature.
/// Asserts declaration has an associated `Atom`.
/// Returns the index into the list of types.
pub fn storeDeclType(zig_object: *ZigObject, gpa: std.mem.Allocator, decl_index: InternPool.DeclIndex, func_type: std.wasm.Type) !u32 {
    const decl_info = zig_object.decls_map.get(decl_index).?;
    const index = try zig_object.putOrGetFuncType(gpa, func_type);
    try zig_object.atom_types.put(gpa, decl_info.atom, index);
    return index;
}

/// The symbols in ZigObject are already represented by an atom as we need to store its data.
/// So rather than creating a new Atom and returning its index, we use this oppertunity to scan
/// its relocations and create any GOT symbols or function table indexes it may require.
pub fn parseSymbolIntoAtom(zig_object: *ZigObject, wasm_file: *Wasm, index: Symbol.Index) !Atom.Index {
    const gpa = wasm_file.base.comp.gpa;
    const loc: Wasm.SymbolLoc = .{ .file = zig_object.index, .index = index };
    const atom_index = wasm_file.symbol_atom.get(loc).?;
    const final_index = try wasm_file.getMatchingSegment(zig_object.index, index);
    try wasm_file.appendAtomAtIndex(final_index, atom_index);
    const atom = wasm_file.getAtom(atom_index);
    for (atom.relocs.items) |reloc| {
        const reloc_index: Symbol.Index = @enumFromInt(reloc.index);
        switch (reloc.relocation_type) {
            .R_WASM_TABLE_INDEX_I32,
            .R_WASM_TABLE_INDEX_I64,
            .R_WASM_TABLE_INDEX_SLEB,
            .R_WASM_TABLE_INDEX_SLEB64,
            => {
                try wasm_file.function_table.put(gpa, .{
                    .file = zig_object.index,
                    .index = reloc_index,
                }, 0);
            },
            .R_WASM_GLOBAL_INDEX_I32,
            .R_WASM_GLOBAL_INDEX_LEB,
            => {
                const sym = zig_object.symbol(reloc_index);
                if (sym.tag != .global) {
                    try wasm_file.got_symbols.append(gpa, .{
                        .file = zig_object.index,
                        .index = reloc_index,
                    });
                }
            },
            else => {},
        }
    }
    return atom_index;
}

/// Creates a new Wasm function with a given symbol name and body.
/// Returns the symbol index of the new function.
pub fn createFunction(
    zig_object: *ZigObject,
    wasm_file: *Wasm,
    symbol_name: []const u8,
    func_ty: std.wasm.Type,
    function_body: *std.ArrayList(u8),
    relocations: *std.ArrayList(types.Relocation),
) !Symbol.Index {
    const gpa = wasm_file.base.comp.gpa;
    const sym_index = try zig_object.allocateSymbol(gpa);
    const sym = zig_object.symbol(sym_index);
    sym.tag = .function;
    sym.name = try zig_object.string_table.insert(gpa, symbol_name);
    const type_index = try zig_object.putOrGetFuncType(gpa, func_ty);
    sym.index = try zig_object.appendFunction(gpa, .{ .type_index = type_index });

    const atom_index = try wasm_file.createAtom(sym_index, zig_object.index);
    const atom = wasm_file.getAtomPtr(atom_index);
    atom.size = @intCast(function_body.items.len);
    atom.code = function_body.moveToUnmanaged();
    atom.relocs = relocations.moveToUnmanaged();

    try zig_object.synthetic_functions.append(gpa, atom_index);
    return sym_index;
}

/// Appends a new `std.wasm.Func` to the list of functions and returns its index.
fn appendFunction(zig_object: *ZigObject, gpa: std.mem.Allocator, func: std.wasm.Func) !u32 {
    const index: u32 = if (zig_object.functions_free_list.popOrNull()) |idx|
        idx
    else idx: {
        const len: u32 = @intCast(zig_object.functions.items.len);
        _ = try zig_object.functions.addOne(gpa);
        break :idx len;
    };
    zig_object.functions.items[index] = func;

    return index;
}

pub fn flushModule(zig_object: *ZigObject, wasm_file: *Wasm) !void {
    try zig_object.populateErrorNameTable(wasm_file);
    try zig_object.setupErrorsLen(wasm_file);
}

const build_options = @import("build_options");
const builtin = @import("builtin");
const codegen = @import("../../codegen.zig");
const link = @import("../../link.zig");
const log = std.log.scoped(.zig_object);
const std = @import("std");
const types = @import("types.zig");

const Air = @import("../../Air.zig");
const Atom = @import("Atom.zig");
const Dwarf = @import("../Dwarf.zig");
const File = @import("file.zig").File;
const InternPool = @import("../../InternPool.zig");
const Liveness = @import("../../Liveness.zig");
const Module = @import("../../Module.zig");
const StringTable = @import("../StringTable.zig");
const Symbol = @import("Symbol.zig");
const Type = @import("../../type.zig").Type;
const Value = @import("../../Value.zig");
const Wasm = @import("../Wasm.zig");
const ZigObject = @This();
